classdef VideoLogic < Logic

   % DEFINE PROPERTIES
  properties (SetAccess = public)
    VideoReady = 0;
    VideoActive = 0;
  end 
  
  methods
    % CONSTRUCTOR
    function O = VideoLogic(varargin)
      global CG;
      O = setName(O,'Video');
           
      ModuleTypes = {'Arduino','Video'};
      ModuleNames = {'ArduinoCAM','PointGrey'};
      ModulePars = {[],[]};
      IDs = addModules(O,ModuleTypes,ModuleNames,ModulePars);
      O.HW.IDs.ArduinoCam = IDs(1);      
      O.HW.IDs.Camera = IDs(2);
      
      % CAMERA RELATED
      O.HW.Camera = CG.Parameters.Setup.Camera;
      O.HW.Camera.TriggerPeriod = floor(1e6/CG.Sessions.Video(O.HW.IDs.Camera).SR);
      
      % POSITION SENSOR OF CAMERA
      cP = CG.Parameters.Setup.Camera;
      O.setInputChannel(cP.Device{1},CG.Parameters.Setup.DAQID ,...
        cP.PositionSensors{1},cP.PositionSensorThresholds(1),...
        ['CamPosC1']);
      
       % ATTACH SENSOR CHANNELS TO NIDAQ 
      O.setInputChannel('NI',CG.Parameters.Setup.DAQID,'ai10',NaN,['CamTrigTo']);
      O.setInputChannel('NI',CG.Parameters.Setup.DAQID,'ai11',NaN,['CamTrigFrom']);
      O.setInputChannel('NI',CG.Parameters.Setup.DAQID,'ai12',2.5,['CamStart']);
      
      % ADD CAMERA TRIGGER CHANNEL TO DAQ SESSION
      O.setDOutputChannel('NI',CG.Parameters.Setup.DAQID,CG.Parameters.Setup.Camera.CameraChannel,'Camera');
      C_setDigitalNI(O.HW.IDs.DAQ,'Camera',0);
      O.setOutputChannel('NI',CG.Parameters.Setup.DAQID,'ao0',['CamTrig']);
      
      % ESTABLISH TRIGGER CONNECTION BETWEEN CAMERA TRIGGER AND
      addTriggerConnection(CG.Sessions.NI(O.HW.IDs.DAQ).SAO,...
        ['External'],... % FROM
        [cP.Device{2},'/',cP.TriggerChannel],... % TO
        'StartTrigger');
    end
      
    % INITIALIZE THE PLATFORMS (MOVE TO INITIAL POSITION)
    function initializeCamera(O)
      global CG
      cInput = O.getInputByName('CamPosC1');
      cStepSize = CG.Parameters.Setup.Camera.StepSize;
      O.updateStates;
      InputCovered = ~O.States.InputStates(cInput);
      WaitTime = O.HW.Camera.WaitTime;
      %InterStepTime = StepTime;
      % QUICKLY Move Platform Inward
      while ~InputCovered
        O.moveCamera(O.HW.Camera.MotorID,-cStepSize);
        pause(WaitTime); % while  CG.Sessions.Arduino(O.HW.ControlID).BusyState pause(InterStepTime); end
        O.updateStates;
        InputCovered = ~O.States.InputStates(cInput);
      end
      % QUICKLY Move Platform Outward
      while InputCovered
        O.moveCamera(O.HW.Camera.MotorID,cStepSize);
        pause(WaitTime); %while  CG.Sessions.Arduino(O.HW.ControlID).BusyState pause(InterStepTime); end
        O.updateStates;
        InputCovered = ~O.States.InputStates(cInput);
      end
      % FINE ADJUSTMENT UNTIL Input JUST COVERED
      while ~InputCovered % SLOWLY Move Platform Inward
        O.moveCamera(O.HW.Camera.MotorID,-cStepSize/2);
        pause(WaitTime/2); %while  CG.Sessions.Arduino(O.HW.ControlID).BusyState pause(InterStepTime); end
        O.updateStates;
        InputCovered = ~O.States.InputStates(cInput);
      end
      O.HW.Camera.Position = 0;
    end
      
    % MOVE THE CAMERA
    function moveCamera(O,CameraID,Distance)
      % MAKE SURE WE ARE NOT IN GENERATE MODE
      DistanceSteps = -Distance*O.HW.Camera.MotorMM2Step;
      C_sendMessageArduino(O.HW.IDs.ArduinoCam,...
        'movemotor', {CameraID, sign(DistanceSteps), abs(DistanceSteps) });
      O.HW.Camera.Position = O.HW.Camera.Position + Distance;
    end
     
    % PREPARE VIDEO
    function prepareVideo(O,ID)
      global CG;
      
      DAQID = CG.Parameters.Setup.DAQID;
      
      % GENERATE 1kHz IN ARDUINO
      TrigRate = 1000;
      C_sendMessageArduino(O.HW.IDs.ArduinoCam,'generatepulses',{1000000*1/TrigRate});
      
      % GENERATE FRAMES FOR 1 SECOND
      %       if ~isfield(O.HW,'FrameTriggers')
      %         cP = CG.Parameters.Setup.Camera;
      %         SR = CG.Sessions.NI(DAQID).SAO.Rate;
      %         Duration = 60; NSamples = Duration*SR;
      %         O.HW.FrameTriggers = zeros(NSamples,1);
      %         PulseLengthSamples = SR*cP.PulseLength;
      %         for i=1:NSamples
      %           if mod(i,SR/1000) < PulseLengthSamples
      %             O.HW.FrameTriggers(i) = cP.PulseVoltage;
      %           end
      %         end
      %       end
      
      % START AND SETUP FOR THE FIRST TRIAL
      if strcmp(CG.Sessions.Video(ID).S.Logging,'off');   C_prepareVideo(ID,'external');   end
      O.VideoReady = 1;
      
      % QUEUE SUFFICIENT DATA TO START THE OUTPUT
      %       if  CG.Sessions.NI(DAQID).SAO.IsRunning stop(CG.Sessions.NI(DAQID).SAO); end
      %       CG.Sessions.NI(DAQID).SAO.TriggersPerRun = 1;
      %       CG.Sessions.NI(DAQID).SAO.queueOutputData(O.HW.FrameTriggers);
      %       try
      %         CG.Sessions.NI(DAQID).SAO.startBackground;
      %       catch
      %         CG.Sessions.NI(DAQID).SAO.queueOutputData(O.HW.FrameTriggers);
      %         CG.Sessions.NI(DAQID).SAO.startBackground;
      %       end
        
    end
    
    % START VIDEO ACQUISITION
    function startVideo(O,ID)
      global CG;
      % SEND START TRIGGER
      C_setDigitalNI(O.HW.IDs.DAQ,'Camera',1);
      set(CG.GUI.Main.Modules.Video(ID).StartButton,'Value',1,'BackgroundColor',[1,0,0]);
      pause(0.025);
      O.VideoActive = 1; O.VideoReady = 0;
    end

    % STOP VIDEO
    function stopVideo(O,ID)
      O.pauseVideo(ID);
      C_stopVideo(ID);
    end 
    
     % STOP VIDEO 
    function pauseVideo(O,ID)
      global CG;      
      disp('Video Stopping!');
      C_setDigitalNI(O.HW.IDs.DAQ,'Camera',0);
      %       stop(CG.Sessions.NI(O.HW.IDs.DAQ).SAO);
      O.VideoActive = 0;
      O.VideoReady = 0;
      set(CG.GUI.Main.Modules.Video(ID).StartButton,'Value',1,'BackgroundColor',[0,1,0]);
      pause(0.01);
      disp('Video Stopped!');
    end 
    
    function selectFramesForSaving(O,SaveVideo)
      global CG;
      try
        if ~SaveVideo
          CG.Data.Video(O.HW.IDs.Camera).RegionsToExclude = [];
        else
          SRAI = CG.Sessions.NI(O.HW.IDs.DAQ).SAI.Rate;
          % FIND START & END POSITION OF THE CURRENT TRIAL
          TrialLimsFound = 0;
          TrialInd = O.getInputByName(['Trial']);
          NIEvents = CG.Events.NI(O.HW.IDs.DAQ).Events;
          EventChannels = fliplr([NIEvents.Data]);
          Ind = find(EventChannels==TrialInd,2,'first');
          if ~isempty(Ind) && strcmp(NIEvents(end-Ind(1)+1).Name,'AI_to_Low')
            TrialEndPos = NIEvents(end-Ind(1)+1).Time(2);
            TrialStartPos = NIEvents(end-Ind(2)+1).Time(2);
          else
            TrialEndPos = CG.Sessions.NI(O.HW.IDs.DAQ).PacketsAcquired;
            TrialStartPos = NIEvents(end-Ind(1)+1).Time(2);
          end
          
          % FIND FRAME NUMBERS AS A FUNCTION OF TIME
          CamStart =  O.getInputByName(['CamStart']);
          cData = CG.Data.NI(O.HW.IDs.DAQ).Analog(TrialStartPos:TrialEndPos,CamStart);
          cData = (cData/2.5) > 1;
          CamStartPos = find(diff(cData)==1);
          if isempty(CamStartPos)
            CamStartPos = TrialStartPos; % CAMERA WAS ON THE ENTIRE TIME DURING THE TRIAL
          else
            CamStartPos = CamStartPos + TrialStartPos; % INDEX FOR START OF CAMERA
          end
          
          % LOOK FOR SECTIONS WHERE THE SENSOR IS COVERED
          HostID = O.HW.PlatformIDs.Host;
          FrontSensorInd = O.getInputByName(['AniPosP',num2str(HostID),'S3']); % USE GUEST/HOST INFORMATION
          cData = CG.Data.NI(O.HW.IDs.DAQ).Analog(CamStartPos:TrialEndPos,FrontSensorInd);
          Threshold = O.HW.Inputs(FrontSensorInd).Threshold;
          cData = (cData/Threshold) > 1; % DIGITIZE
          cIndAbsent = find(diff(cData)==1); % INDICES FOR START OF  SENSOR UNCOVER
          cIndPresent = find(diff(cData)==-1);   % INDICES FOR START OF SENSOR COVER
          if isempty(cIndAbsent)
            if sum(cData) == length(cData) % ANIMAL NEVER AT FRONT
              cIndAbsent = 1; % CUT OUT EVERYTHING
            end
          end
          % FIND REGIONS TO CUT OUT
          RegionsToExclude = {};
          CamStartTime = CG.Data.NI(O.HW.IDs.DAQ).Time(CamStartPos,1);
          for iU=1:length(cIndAbsent)
            iD = find(cIndPresent>cIndAbsent(iU),1,'first');
            if ~isempty(iD)
              if (cIndPresent(iD) - cIndAbsent(iU)) > (1*SRAI) % If the Animals is gone for more than 1s
                FirstTime = CG.Data.NI(O.HW.IDs.DAQ).Time(CamStartPos + cIndAbsent(iU),1) - CamStartTime;
                LastTime = CG.Data.NI(O.HW.IDs.DAQ).Time(CamStartPos + cIndPresent(iD),1) - CamStartTime;
                RegionsToExclude{end+1} = [FirstTime,LastTime];
                disp(['Excluding VideoFrames from ',num2str(FirstTime),'s to ' num2str(LastTime),'s (from Camera Start).']);
              end
            else % ANIMAL DID NOT RETURN
              if TrialEndPos - (CamStartPos + cIndAbsent(iU)) > (0.5*SRAI) % If the last uncover happend more than half a second before trial end
                FirstTime = CG.Data.NI(O.HW.IDs.DAQ).Time(CamStartPos + cIndAbsent(iU),1) - CamStartTime;
                RegionsToExclude{end+1} = [FirstTime,inf];
                disp(['Excluding VideoFrames from ',num2str(FirstTime),'s to end of trial (from Camera Start).']);
              end
            end
          end
          CG.Data.Video(O.HW.IDs.Camera).RegionsToExclude = RegionsToExclude;
        end
      catch Exception
        disp('Error occurred in selectFramesForSaving'); keyboard;
      end
    end
 
  end
end